Welcome Guest | Sign in | Register
Vriables and Data Storage - C Programming Interview Questions and Answers | LucentBlackBoard | LucentBlackBoard.com

Home > Technical Interviews > Computer Science & Engineering > C Programming > Vriables and Data Storage Questions and Answers

6. When should the volatile modifier be used?

The volatile modifier is a directive to the compiler's optimizer that operations involving this variable should not be optimized in certain ways. There are two special cases in which use of the volatile modifier is desirable. The first case involves memory-mapped hardware (a device such as a graphics adaptor that appears to the computer's hardware as if it were part of the computer's memory), and the second involves shared memory (memory used by two or more programs running simultaneously).
Most computers have a set of registers that can be accessed faster than the computer's main memory. A good compiler will perform a kind of optimization called "redundant load and store removal." The compiler looks for places in the code where it can either remove an instruction to load data from memory because the value is already in a register, or remove an instruction to store data to memory because the value can stay in a register until it is changed again anyway.
If a variable is a pointer to something other than normal memory, such as memory-mapped ports on a peripheral, redundant load and store optimizations might be detrimental. For instance, here's a piece of code that might be used to time some operation:
time_t time_addition(volatile const struct timer *t, int a)
{
int n;
int x;
time_t then;
x = 0;
then = t->value;
for (n = 0; n < 1000; n++)
{
x = x + a;
}
return t->value - then;
}
In this code, the variable t->value is actually a hardware counter that is being incremented as time passes. The function adds the value of a to x 1000 times, and it returns the amount the timer was incremented by while the 1000 additions were being performed.
Without the volatile modifier, a clever optimizer might assume that the value of t does not change during the execution of the function, because there is no statement that explicitly changes it. In that case, there's no need to read it from memory a second time and subtract it, because the answer will always be 0. The compiler might therefore "optimize" the function by making it always return 0.
If a variable points to data in shared memory, you also don't want the compiler to perform redundant load and store optimizations. Shared memory is normally used to enable two programs to communicate with each other by having one program store data in the shared portion of memory and the other program read the same portion of memory. If the compiler optimizes away a load or store of shared memory, communication between the two programs will be affected.

7. Can a variable be both const and volatile?

Yes. The const modifier means that this code cannot change the value of the variable, but that does not mean that the value cannot be changed by means outside this code. For instance, the timer structure was accessed through a volatile const pointer. The function itself did not change the value of the timer, so it was declaredconst. However, the value was changed by hardware on the computer, so it was declared volatile. If a variable is both const and volatile, the two modifiers can appear in either order.

8. When should the const modifier be used?

There are several reasons to use const pointers. First, it allows the compiler to catch errors in which code accidentally changes the value of a variable, as in
while (*str = 0) /* programmer meant to write *str != 0 */
{
/* some code here */
str++;
}
in which the = sign is a typographical error. Without the const in the declaration of str, the program would compile but not run properly.
Another reason is efficiency. The compiler might be able to make certain optimizations to the code generated if it knows that a variable will not be changed.
Any function parameter which points to data that is not modified by the function or by any function it calls should declare the pointer a pointer to const. Function parameters that are passed by value (rather than through a pointer) can be declared const if neither the function nor any function it calls modifies the data.
In practice, however, such parameters are usually declared const only if it might be more efficient for the compiler to access the data through a pointer than by copying it.

9. How reliable are floating-point comparisons?

Floating-point numbers are the "black art" of computer programming. One reason why this is so is that there is no optimal way to represent an arbitrary number. The Institute of Electrical and Electronic Engineers (IEEE) has developed a standard for the representation of floating-point numbers, but you cannot guarantee that every machine you use will conform to the standard.
Even if your machine does conform to the standard, there are deeper issues. It can be shown mathematically that there are an infinite number of "real" numbers between any two numbers. For the computer to distinguish between two numbers, the bits that represent them must differ. To represent an infinite number of different bit patterns would take an infinite number of bits. Because the computer must represent a large range of numbers in a small number of bits (usually 32 to 64 bits), it has to make approximate representations of most numbers.
Because floating-point numbers are so tricky to deal with, it's generally bad practice to compare a floating- point number for equality with anything. Inequalities are much safer. If, for instance, you want to step through a range of numbers in small increments, you might write this:
#include
const float first = 0.0;
const float last = 70.0;
const float small = 0.007;
main()
{
float f;
for (f = first; f != last && f < last + 1.0; f += small)
;
printf("f is now %g\n", f);
}
However, rounding errors and small differences in the representation of the variable small might cause f to never be equal to last (it might go from being just under it to being just over it). Thus, the loop would go past the value last. The inequality f < last + 1.0 has been added to prevent the program from running on for a very long time if this happens. If you run this program and the value printed for f is 71 or more, this is what has happened.
A safer way to write this loop is to use the inequality f < last to test for the loop ending, as in this example:
float f;
for (f = first; f < last; f += small)
;
You could even precompute the number of times the loop should be executed and use an integer to count iterations of the loop, as in this example:
float f;
int count = (last - first) / small;
for (f = first; count-- > 0; f += small)

10. How can you determine the maximum value that a numeric variable can hold?

The easiest way to find out how large or small a number that a particular type can hold is to use the values defined in the ANSI standard header file limits.h. This file contains many useful constants defining the values that can be held by various types, including these:
Value -  Description
CHAR_BIT - Number of bits in a char
CHAR_MAX - Maximum decimal integer value of a char
CHAR_MIN - Minimum decimal integer value of a char
MB_LEN_MAX - Maximum number of bytes in a multibyte character
INT_MAX - Maximum decimal value of an int
INT_MIN - Minimum decimal value of an int
LONG_MAX - Maximum decimal value of a long
LONG_MIN - Minimum decimal value of a long
SCHAR_MAX - Maximum decimal integer value of a signed char
SCHAR_MIN - Minimum decimal integer value of a signed char
SHRT_MAX - Maximum decimal value of a short
SHRT_MIN - Minimum decimal value of a short
UCHAR_MAX - Maximum decimal integer value of unsigned char
UINT_MAX - Maximum decimal value of an unsigned integer
ULONG_MAX - Maximum decimal value of an unsigned long int
USHRT_MAX - Maximum decimal value of an unsigned short int
For integral types, on a machine that uses two's complement arithmetic (which is just about any machine you're likely to use), a signed type can hold numbers from -2(number of bits - 1) to +2(number of bits - 1) - 1.
An unsigned type can hold values from 0 to +2(number of bits)- 1. For instance, a 16-bit signed integer can hold numbers from -215(-32768) to +215 - 1 (32767).




Partner Sites
LucentBlackBoard.com                  SoftLucent.com                  LucentJobs.com
All rights reserved © 2012-2015 SoftLucent.